//============================================================================
// Copyright (C) 2009-2013 Brett R. Jones
// All Rights Reserved
//
// You may redistribute and/or modify for non commercial and commercial uses
// provided this copyright notice remains in place and is not modified
//
// This code is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// brettjones1900@gmail.com
// http://www.p2panarchy.com
//============================================================================

#include "RcWebServerLib.h"
#include <VxUtilLib/VxGlobals.h>
#include <time.h>
#include <memory.h>

//============================================================================
//=== Declaration of functions used only in this file ===//
//============================================================================


//============================================================================
//=== constructor ===//
RcWebServer::RcWebServer()
	: m_poWebPlugin(0)
	, m_bWebServerEnabled( false )
	, m_bShuttingDown( false )
{
	// empty
}
//============================================================================
//=== destructor ===//
RcWebServer::~RcWebServer()
{
	// empty
}

//============================================================================
void RcWebServer::setPluginWebServer( RcPluginWebServer * poPlugin ) 
{ 
	m_poWebPlugin = poPlugin; 
}

//============================================================================
//! set root directory of web site
void RcWebServer::setWebSiteDir( const char * pWebSiteDir ) 
{ 
	m_strWebSiteDir = pWebSiteDir; 
} 

//============================================================================
//! get root directory of web site
std::string& RcWebServer::getWebSiteDir(  void ) 
{ 
	return m_strWebSiteDir; 
}

//============================================================================
//=== overrides ===//
//============================================================================
//! handle callbacks from sockets
void RcWebServer::DoRecieveCallback( VxSktBase * poSkt )
{
	this->HandleWebData( (RcWebSkt *) poSkt );
}

//============================================================================
//=== methods ===//
//============================================================================
//============================================================================
//! return true if web server is available
bool RcWebServer::isWebServerAvailable( void )
{
	#ifdef USE_WEB_FILESHARE
		return ( (false == VxIsAppShuttingDown() ) && 
			(true == m_bWebServerEnabled ) &&
			(100 == GetBuildProgress() ) );
	#else
		return (	(false == VxIsAppShuttingDown() ) && 
			(true == m_bWebServerEnabled ) );
	#endif //USE_WEB_FILESHARE
}

//============================================================================
// shutdown web server
void RcWebServer::webServerShutdown( void )
{
	log_msg( LOG_SKT, "RcWebServer::webServerShutdown\n" );
	SetWebServerEnable( false );
	ShutdownAllSkts();
	log_msg( LOG_SKT, "RcWebServer::webServerShutdown done\n" );
}

//============================================================================
//! set enable
void RcWebServer::SetWebServerEnable( bool bEnable )
{
	m_bWebServerEnabled = bEnable;
}

//============================================================================
// accept connection
RCODE RcWebServer::acceptRawLocalHttpConnection( VxSktBase * poSktIn, VxNetIdent * poIdent, RcWebSkt ** ppoRetWebSkt )
{
	* ppoRetWebSkt = NULL;
	if( false == isWebServerAvailable() )
	{
		// we are shutting down or build is in progress
		return -1;
	}
	if( poSktIn )
	{
		RcWebSkt * poSkt		= new RcWebSkt();
		poSkt->setWebServerPlugin( m_poWebPlugin );

		poSkt->setIdentity( poIdent );

		poSkt->m_eSktType		= eSktTypeTcpAccept;
		poSkt->m_oSocket		= poSktIn->m_oSocket;
		poSktIn->m_oSocket		= INVALID_SOCKET;

		poSkt->m_strRmtIp		= poSktIn->m_strRmtIp; 
		poSkt->m_oRmtIp			= poSktIn->m_oRmtIp; 

		poSkt->m_strLclIp		= poSktIn->m_strLclIp; 
		poSkt->m_oLclIp			= poSktIn->m_oLclIp; 

		// copy any data from the other socket
		int iDataLen = poSktIn->GetSktBufDataLen();
		if( iDataLen )
		{
			char * pTempRead = poSktIn->GetSktReadBuf();
			U8 * pTempWrite = poSkt->GetSktWriteBuf();
			memcpy( pTempWrite, pTempRead, iDataLen );
			poSkt->SktBufAmountWrote( iDataLen );
			poSkt->m_iLastRxLen = iDataLen;
			poSktIn->SktBufAmountRead( iDataLen );
		}

		//VxVerifyCodePtr( m_pfnOurRecieve );
		poSkt->SetRecieveCallback( m_pfnOurRecieve );
		poSkt->SetTransmitCallback( m_pfnOurTransmit );
		poSkt->SetCallbackReason( eSktCallbackReasonConnecting );
		poSkt->m_poSktMgr		= this;
		poSkt->m_poWebMgr		= this;
		poSkt->m_bIsConnected	= true;

		AddSkt( poSkt );

		log_msg( LOG_SKT,  "skt %d to %s raw accept from skt %d\n", poSkt->m_iSktId, poSkt->m_strRmtIp.c_str(), poSktIn->m_iSktId );

		* ppoRetWebSkt = poSkt;
		return 0;
	}
	return -1;
}

//============================================================================
//! start skt threads for accepted web skt
void RcWebServer::startWebSktThreads( RcWebSkt * poWebSkt )
{
	poWebSkt->m_oSktRxThread.StartThread(  (VX_THREAD_FUNCTION_T)RcSktWebRecieveThreadFunc, poWebSkt, "RcWebSktRx" );
	poWebSkt->m_oSktTxThread.StartThread(  (VX_THREAD_FUNCTION_T)RcSktWebTransmitThreadFunc, poWebSkt, "RcWebSktTx" );
}

//============================================================================
//! thread function to receive data from socket
U32 RcSktWebRecieveThreadFunc(  void * pvContext )
{
	VxThread * poThread = (VxThread *)pvContext;
	poThread->SetIsThreadRunning( true );
	VxSktBase * poSkt = (VxSktBase *)poThread->GetUserParam();

	log_msg( LOG_SKT,  "skt %d %s SktWebRecieveThreadFunc start\n", poSkt->m_iSktId, poSkt->m_strRmtIp.c_str() );

	char as8Buf[ 0x8000 ];
	int iDataLen;
	int iBufferAlmostFull = poSkt->GetSktBufSize() - poSkt->GetSktBufSize()/ 10;
	BOOL bIsUdpSkt = true;
	if( eSktTypeTcpConnect == poSkt->GetSktType() ||
		eSktTypeTcpAccept == poSkt->GetSktType() )
	{
		bIsUdpSkt = false;
	}
	if(	( poThread->isAborted() ) ||
		( INVALID_SOCKET == poSkt->m_oSocket ) )
	{
		// something has already happened to the connection
		//! Thread calls this just before exit
		poThread->ThreadAboutToExit();
		return 0;
	}
	poSkt->m_bIsConnected = true;
	poSkt->m_eSktCallbackReason = eSktCallbackReasonConnected;
	//VxVerifyCodePtr( poSkt->m_pfnRecieve );
	poSkt->m_pfnRecieve( poSkt );
	poSkt->m_eSktCallbackReason = eSktCallbackReasonData;
	if( poSkt->GetSktBufDataLen() )
	{
		//VxVerifyCodePtr( poSkt->m_pfnRecieve );
		poSkt->m_pfnRecieve( poSkt );
	}

	poSkt->SetSktBlocking( true );

	while(	( false == poThread->isAborted() ) && 
			( INVALID_SOCKET != poSkt->m_oSocket ),
			( eSktCallbackReasonData == poSkt->m_eSktCallbackReason ) )
	{
		int iAttemptLen = poSkt->GetSktBufFreeSpace();
		vx_assert( iAttemptLen >= 0 );
		vx_assert( iAttemptLen <= poSkt->m_iSktBufSize );
		if( iAttemptLen >= (int)sizeof( as8Buf ) )
		{
			iAttemptLen = (int)sizeof( as8Buf ) - 16;
		}

		if( iAttemptLen < iBufferAlmostFull ) 
		{
			// socket buffer is almost full.. let app empty some out
			VxSleep( 200 );
			continue;
		}
		log_msg( LOG_SKT,  "skt %d SktWebRecieveThreadFunc wait for recv\n", poSkt->m_iSktId );
		if( bIsUdpSkt )
		{
			struct sockaddr_storage oAddr;
            socklen_t iAddrLen = sizeof(oAddr);

			iDataLen = recvfrom(	poSkt->m_oSocket,	// socket
									as8Buf,				// buffer to read into
									iAttemptLen,		// length of buffer space
									0,					// flags
									(struct sockaddr*)&oAddr,				// source address
									&iAddrLen );		// size of address structure
			if( iDataLen > 0 )
			{
				poSkt->m_oRmtIp.setIpAndPort( oAddr );
				poSkt->m_strRmtIp = poSkt->m_oRmtIp.toStdString();

				if( poSkt->m_oRxKey.IsKeySet() )
				{
					if( false == poSkt->m_oRxKey.IsValidDataLen( iDataLen ) )
					{
						// throw away the data because not valid length
						continue;
					}
					// decrypt the data
					poSkt->m_oRxCrypto.ImportKey( &poSkt->m_oRxKey );

				}
			}
		}
		else
		{
			iDataLen = recv(		poSkt->m_oSocket,	// socket
									as8Buf,				// buffer to read into
									iAttemptLen,		// length of buffer space
									0 );				// flags
		}
		log_msg( LOG_SKT,  "skt %d SktWebRecieveThreadFunc wait for recv done len %d\n", poSkt->m_iSktId, iDataLen );
		if( poThread->isAborted() || 
			( eSktCallbackReasonData != poSkt->m_eSktCallbackReason ) || 
			( INVALID_SOCKET == poSkt->m_oSocket ) ||
			( iDataLen <= 0 ) )
		{
			if( ( iDataLen < 0 ) &&
				( false == poThread->isAborted() ) )
			{
				// socket error occurred
				poSkt->m_rcLastError = VxGetLastError();
				if( 0 == poSkt->m_rcLastError )
				{
					poSkt->m_rcLastError = -1;
				}
			}
			else
			{
				poSkt->m_rcLastError = 0;
			}
			break;
		}
		if( iDataLen > 0 )
		{
			memcpy( poSkt->GetSktWriteBuf(), as8Buf, iDataLen );
			poSkt->SktBufAmountWrote( iDataLen );
			poSkt->m_iLastRxLen = iDataLen;
			poSkt->RxedPkt( iDataLen );
			// call back user with the good news.. we have data
			//VxVerifyCodePtr( poSkt->m_pfnRecieve );
			poSkt->m_pfnRecieve( poSkt );
		}
	}
	if( poSkt->m_rcLastError )
	{
		// we had a error
		poSkt->m_eSktCallbackReason = eSktCallbackReasonError;
		//VxVerifyCodePtr( poSkt->m_pfnRecieve );
		poSkt->m_pfnRecieve( poSkt );
	}
	poSkt->m_eSktCallbackReason = eSktCallbackReasonClosing;
	//VxVerifyCodePtr( poSkt->m_pfnRecieve );
	poSkt->m_pfnRecieve( poSkt );
	if( false == poThread->isAborted() )
	{
		// we are closing due to error .. not because user called close
		poSkt->m_bClosingFromRxThread = true;
		poSkt->CloseSkt();
	}
	poSkt->m_eSktCallbackReason = eSktCallbackReasonClosed;
	//VxVerifyCodePtr( poSkt->m_pfnRecieve );
	poSkt->m_pfnRecieve( poSkt );

	//! Thread calls this just before exit
	poThread->ThreadAboutToExit();

	log_msg( LOG_SKT,  "skt %d %s SktWebRecieveThreadFunc exit\n", poSkt->m_iSktId, poSkt->m_strRmtIp.c_str() );

	return 0;
}

//============================================================================
bool RcWebServer::isExecuteableFile( std::string& strWebFileName )
{
	bool bIsExe = false;
	if(strWebFileName.length() >= 4)
	{
		const char * pTemp = (const char *)strWebFileName.c_str();
		const char * pExt = &pTemp[strWebFileName.length() - 4];
		if( (0 == stricmp( ".exe", pExt)) ||
			(0 == stricmp( ".com", pExt)) )
		{
			bIsExe = true;
		}
	}
	return bIsExe;
}

#ifdef USE_WEB_FILESHARE
//============================================================================
//! return true if password has been set or no password required
BOOL RcWebServer::IsPasswordOk( U32 u32IpAddr, EWbMyFiles eFileType )
{
	if( false == IsMyFilesPwdProtected( eFileType ) )
	{
		return false;
	}
	BOOL bPwdOk = false;
	std::vector<VxSharedFilesAuth>::iterator oIter;

	m_oSktMgrMutex.Lock(__FILE__,__LINE__);
	for( oIter = m_aoAuthorizations.begin(); oIter != m_aoAuthorizations.end(); ++oIter )
	{
		if( (*oIter).m_u32Ip == u32IpAddr )
		{
			bPwdOk =  (*oIter).m_aoPwds[ eFileType ].m_strPwd.size()?1:0;
			break;
		}
	}
	m_oSktMgrMutex.Unlock(__FILE__,__LINE__);
	return bPwdOk;
}

//============================================================================
//! verify the password is correct for the given file share type
BOOL RcWebServer::VerifyPwd( U32 u32IpAddr, EWbMyFiles eFileType, const char * pPwd )
{
	if( false == IsMyFilesPwdProtected( eFileType ) )
	{
		return true;
	}
	BOOL	bPwdOk		= m_aoMyFiles[ eFileType ].VerifyPwd( pPwd );
	BOOL	bFoundEntry = false;
	U32		u32CurTime	= time( NULL );

	std::vector<VxSharedFilesAuth>::iterator oIter;
	m_oSktMgrMutex.Lock(__FILE__,__LINE__);
	for( oIter = m_aoAuthorizations.begin(); oIter != m_aoAuthorizations.end(); ++oIter )
	{
		if( (*oIter).m_u32Ip == u32IpAddr )
		{
			bFoundEntry = true;
			(*oIter).m_u64LastAttemptTime = u32CurTime;
			if( bPwdOk )
			{
				(*oIter).m_aoPwds[ eFileType ].m_strPwd = pPwd;
				(*oIter).m_aoPwds[ eFileType ].m_iPwdAttempts = 0;
			}
			else
			{
				(*oIter).m_aoPwds[ eFileType ].m_iPwdAttempts++;
				if( (*oIter).m_aoPwds[ eFileType ].m_iPwdAttempts > 6 )
				{
					VxReportHack( NULL, 2, u32IpAddr, "My Files Password Attempts > 6" );
					(*oIter).m_aoPwds[ eFileType ].m_iPwdAttempts = 0;
				}
			}
			break;
		}
	}
	if( false == bFoundEntry )
	{
		// make a entry for next time
		VxSharedFilesAuth oAuth;
		oAuth.m_u32Ip = u32IpAddr;
		oAuth.m_u64LastAttemptTime = u32CurTime;
		if( bPwdOk )
		{
			oAuth.m_aoPwds[ eFileType ].m_strPwd = pPwd;
		}
		else
		{
			oAuth.m_aoPwds[ eFileType ].m_iPwdAttempts++;
		}
		m_aoAuthorizations.push_back( oAuth );
	}
	m_oSktMgrMutex.Unlock(__FILE__,__LINE__);

	return bPwdOk;
}

//============================================================================
//! called once per second for authorization timeouts
void RcWebServer::WebServerOncePerSecond( void )
{
	// every hour clean out old authorizations
static int iSecCnt = 0;
	iSecCnt++;
	if( iSecCnt < 3600 )
	{
		return;
	}
	// its been a hour
	iSecCnt = 0;

	std::vector<VxSharedFilesAuth>::iterator oIter;
	U64 u64CurTime = time( NULL );

	m_oSktMgrMutex.Lock(__FILE__,__LINE__);
	for( oIter = m_aoAuthorizations.begin(); oIter != m_aoAuthorizations.end(); ++oIter )
	{

		if( u64CurTime - (*oIter).m_u64LastAttemptTime > 3600 )
		{
			m_aoAuthorizations.erase( oIter );
		}
	}
	m_oSktMgrMutex.Unlock(__FILE__,__LINE__);

};
#endif // USE_WEB_FILESHARE
